/*
 * Copyright (C) 2011-2013 Eric Lasota
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef __RDX_PROGRAMMABILITY_HPP__
#define __RDX_PROGRAMMABILITY_HPP__

#include <cstddef>

#include "rdx_pragmas.hpp"
#include "rdx_utility.hpp"
#include "rdx_ilopcodes.hpp"
#include "rdx_callbacks.hpp"

namespace RDX
{
	struct OperationContext;

	namespace ObjectManagement
	{
		struct ITypeProcessor;
		struct ITypeSerializer;
		struct GCInfo;
		struct IObjectManager;
		template<class _T> class CRef;
	};

	namespace ILCompiler
	{
		struct ILInstruction;
		struct ILCallPoint;
		struct ILDebugInfo;
		union ILOpCompactArg;
		union ILOpLargeArg;
		struct StackJournal;
		struct ExceptionHandlerJournal;
	};

	namespace Programmability
	{
		struct StructuredType;
		struct Method;
		struct RuntimeThread;
		union RuntimeStackValue;
		class PrecompiledCodeModule;

		typedef Bool SerializationDetector;
		
		class String
		{
			struct NativeProperties
			{
				LargeInt			length;
				const Char			*characters;
			} _native;

		public:
			inline String(const Char *characters, LargeInt length)
			{
				_native.length = length;
				_native.characters = characters;
			}

			inline bool Equal(const char *str) const
			{
				LargeInt i;
				const Char *carray = _native.characters;
				for(i=0;str[i];i++)
					if(carray[i] != static_cast<Char>(str[i]))
						return false;
				if(i != _native.length)
					return false;
				return true;
			}

			inline bool StartsWith(const char *str) const
			{
				const Char *carray = _native.characters;
				for(LargeInt i=0;str[i];i++)
					if(carray[i] != static_cast<Char>(str[i]))
						return false;
				return true;
			}

			inline bool StartsWith(const String *str) const
			{
				if(!str)
					return false;

				LargeInt rslen = str->_native.length;

				const Char *carray1 = _native.characters;
				const Char *carray2 = str->_native.characters;

				if(_native.length < rslen)
					return false;

				for(LargeInt i=0;i<rslen;i++)
					if(carray1[i] != carray2[i])
						return false;
				return true;
			}

			inline const Char *AsChars() const
			{
				if(this == NULL)
					return NULL;
				return this->_native.characters;
			}

			inline LargeInt Length() const
			{
				return this->_native.length;
			}
		};

		struct Type
		{
		};
				
		struct MethodParameter
		{
			Type	*type;
			Bool	isConstant;
			Bool	isNotNull;
		};

		struct ArrayOfType : public Type
		{
			Type				*type;
			Int					numDimensions;
			Bool				isConstant;
		};

		struct DelegateType : public Type
		{
			MethodParameter		*parameters;
			Type				**returnTypes;
		};

		struct Exception
		{
			Exception *innerException;
		};

		struct InstructionFileInfo
		{
			const String *filename;	// NULL if the same as the previous filename
			Int line;
			Int firstInstruction;
		};

		struct Method
		{
			struct NativeProperties
			{
				struct Patch
				{
					const void *loc;
					bool active;
					bool forbidResume;	// Instruction might be IP after a jump, but is not valid for a thread resume
				};

				ILCompiler::ILInstruction				*ilinstructions;
				const UInt8								*ilResumeFlags;
				ILCompiler::ILCallPoint					*callPoints;
				ILCompiler::ILDebugInfo					*debugInfo;
				ILCompiler::ILOpCompactArg				*compactArgs;
				ILCompiler::ILOpLargeArg				*largeArgs;
				LargeInt								numILInstructions;

				void									*nativeInstructions;
				LargeInt								nativeInstructionSize;
				const PrecompiledCodeModule				*precompiledCodeModule;
				UInt8									*compactedJournals;
				LargeInt								numJournals;
				ILCompiler::ExceptionHandlerJournal		*exHandlers;
				LargeInt								*translation1;
				void									*translation2;

				NativeCallback							nativeCall;
				bool									isNativeCall;
				IPToCurrentInstructionCallback			ipToCurrentInstruction;
				InstrNumToIPCallback					instrNumToIP;
				ResumeThreadCallback					resumeThread;

				bool									intrinsicStateChecked;

				bool									isIntrinsic;
				RDX::ILOpcodes::ILOpcode				opcode;
				LargeInt								p2, p3;
				bool									neverFails;
				bool									isBranching;
				RDX::ILOpcodes::ILOpcode				falseCheckOpcode;
				LargeInt								exceptionInsertionOffset;	// Where in the frame to insert exceptions on catches

				LargeInt								frameCapacity;				// Maximum amount of space this frame can consume.  Aligned to RDX_MAX_ALIGNMENT.
				LargeInt								thisParameterInvokeOffset;	// Where "this" will be if inserted from a precall
			} _native;

			MethodParameter					*parameters;
			Type							**returnTypes;
			Byte							*bytecode;
			void							**resArgs;
			InstructionFileInfo				*instructionFileInfos;
			Int								numInstructions;
			Int								vftIndex;				// 0 for direct calls, index+1 for virtual
			Int								thisParameterOffset;	// 0 for direct calls, param+1 for virtual
			bool							isAbstract;
			
			inline static LargeInt NativeSize() { return RDX_OFFSETOF(Method, parameters); }
			inline static LargeInt NativeAlignment() { return RDX_ALIGNOF(Method::NativeProperties); }
			void DetermineIntrinsicState();
			
			int ExplicitInvoke(OperationContext *ctx, RuntimeThread *t, int timeout) const;
			int Invoke(OperationContext *ctx, RuntimeThread *t, int timeout) const;
		};

		struct Property
		{
			const String		*name;
			Type				*type;
			Bool				isConstant;
			Bool				mustBeConstant;
		};

		struct Enumerant
		{
			const String			*name;
			EnumValue				value;
		};

		struct InterfaceImplementation
		{
			StructuredType		*type;
			Int					vftOffset;
		};

		// Structured types are any types that contain structured data and aren't simply references to other types of info
		struct StructuredType : public Type
		{
			enum StorageSpecifier
			{
				SS_Class,
				SS_RefStruct,
				SS_ValStruct,
				SS_Enum,
				SS_Interface,
			};

			struct NativeProperties
			{
				enum Flags
				{
					STF_StructureEvaluated				= 1,
					STF_DefaultEvaluated				= 2,
					STF_DependencyDefaultsEvaluated		= 4,
					STF_ZeroFill						= 8,	// No complex dependencies, implies DefaultEvaluated and DependencyDefaultsEvaluated
					STF_StructureIsMutable				= 32,	// All members are constant
					STF_AllowBulkSerialize				= 64,	// All members contain only NativeSafeToSerialize structures and no references, this will be bulk serialized
				};

				struct ContainedReference
				{
					LargeInt	offset;
					const Type	*requiredType;
					bool		mustBeConstant;
				};

				
				struct UserProperties
				{
					// User flags are flags that may be set by things that interact with the runtime
					enum UserFlags
					{
						STUF_AllowDuplicates				= 1,	// Permitted in DOMAIN_Duplicable
						STUF_NativeChangesDuringLoad		= 2,	// Native data may change during load so don't compare it for duplicates (only works with ref types)
						STUF_NativeSafeToSerialize			= 4,	// Native data is safe to serialize as binary AND is in reverse byte order on opposite endian systems
						STUF_SerializeAsReference			= 8,	// Serialize references directly, creating instances not allowed
					};

					// Member processor, for marking dependencies, finalizing, etc.
					const ObjectManagement::ITypeProcessor *typeProcessor;
					const ObjectManagement::ITypeSerializer *typeSerializer;
					LargeInt nativeSize;
					LargeInt nativeAlignment;
					UInt8 flags;
				};

				LargeInt				numContainedReferences;
				ContainedReference		*containedReferences;

				LargeInt				*propertyOffsets;
				
				LargeInt size;
				LargeInt alignment;

				void	*actualDefaultValue;
				
				UserProperties user;

				UInt8 flags;
			} _native;

			StructuredType					*parentClass;
			InterfaceImplementation			*interfaces;
			EnumValue						storageSpecifier;
			Method							**virtualMethods;
			Property						*properties;
			Enumerant						*enumerants;
			void							*defaultValue;
			Bool							isFinal;
			Bool							isAbstract;
			Bool							isLocalized;
		};
	}
}

#include "rdx_runtime.hpp"
#include "rdx_longflow.hpp"

namespace RDX
{
	namespace Programmability
	{
		inline int Method::ExplicitInvoke(OperationContext *ctx, RDX::Programmability::RuntimeThread *t, int timeout) const
		{
			Threading::AtomicWrite(&t->timeout, timeout);
			const ICodeProvider *cp = ObjectManagement::GCInfo::From(this)->ownerObjectManager->GetCodeProvider();
			return cp->RunMethod(ctx, t->ownerObjectManager, this, t, t->precallFrame.prv);
		}

		inline int Method::Invoke(OperationContext *ctx, RDX::Programmability::RuntimeThread *t, int timeout) const
		{
			RDX_TRY(ctx)
			{
				// pParametersAndReturnValues is only used to allow compatibility with RDX_MARSHAL macros
				Threading::AtomicWrite(&t->timeout, timeout);
				const Method *callMethod = this;

				if(this->vftIndex)
				{
					const void *objInstance = t->stackBytes + t->stackCapacity + this->_native.thisParameterInvokeOffset;
					if(!objInstance)
						RDX_STHROW(ctx, Errors::RDX_ERROR_INVALID_PARAMETER);
					callMethod = ObjectManagement::GCInfo::From(objInstance)->vft[this->vftIndex - 1];
				}

				const ICodeProvider *cp = ObjectManagement::GCInfo::From(callMethod)->ownerObjectManager->GetCodeProvider();
				return cp->RunMethod(ctx, t->ownerObjectManager, callMethod, t, t->precallFrame.prv);
			}
			RDX_CATCH(ctx)
			{
				RDX_RETHROWV(ctx, 0);
			}
			RDX_ENDTRY
		}
	}
}

#endif
